package com.idea.easy.log.aspect;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;

import com.idea.easy.log.constant.Constant;
import com.idea.easy.log.props.ConsoleLogMode;
import com.idea.easy.log.props.EasyLogProperties;
import com.idea.easy.log.utils.ClassUtil;
import com.idea.easy.log.utils.Func;
import com.idea.easy.log.utils.JsonUtil;
import com.idea.easy.log.utils.WebUtil;
import com.idea.easy.log.utils.str.StrUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.InputStreamSource;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@Aspect
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnProperty(
    value = {"easy-log.enabled"},
    havingValue = "true"
)
@AllArgsConstructor
public class ConsoleLogAspect {

    private final EasyLogProperties properties;

    @Around("(@within(org.springframework.stereotype.Controller) " +
            "|| @within(org.springframework.web.bind.annotation.RestController))")
    public Object consoleLog(ProceedingJoinPoint point) throws Throwable {
        ConsoleLogMode logMode = properties.getConsoleLogMode();
        //如果是NONE模式则直接执行目标方法
        if (ConsoleLogMode.CLOSE.eq(logMode)){
            return point.proceed();
        }
        //获取HttpServletRequest对象
        HttpServletRequest request = WebUtil.getRequest();
        //构建请求的日志信息
        StringBuilder requestBeforeLog = new StringBuilder();
        List<Object> requestBeforeArgs = new ArrayList<>();
        requestBeforeLog.append("\n");
        requestBeforeLog.append(Constant.REQUEST_START_LOG);
        String requestUrl = Objects.requireNonNull(request).getRequestURI();
        String requestMethod = request.getMethod();
        requestBeforeLog.append("===> {}: {} \n");
        requestBeforeArgs.add(requestMethod);
        requestBeforeArgs.add(requestUrl);
        //除了全量模式，其它模式不会打印请求头
        if(ConsoleLogMode.ALL.eq(logMode)){
            addRequestHeader(request,requestBeforeLog,requestBeforeArgs);
        }
        //简单模式不会打印请求参数和请求头
        if (ConsoleLogMode.SIMPLE.gt(logMode)){
            addRequestParams(point,requestBeforeLog,requestBeforeArgs);
        }
        requestBeforeLog.append(Constant.REQUEST_END_LOG);
        log.info(requestBeforeLog.toString(), requestBeforeArgs.toArray());
        //构建响应的日志信息
        StringBuilder responseAfterLog = new StringBuilder(200);
        List<Object> responseAfterArgs = new ArrayList<>();
        responseAfterLog.append("\n");
        responseAfterLog.append(Constant.RESPONSE_START_LOG);
        long start = System.currentTimeMillis();
        Object result;
        try {
            result = point.proceed();
            responseAfterLog.append("== Response Result ==  {} \n");
            responseAfterArgs.add(JsonUtil.toJson(result));
        }finally {
            //使用finally，避免目标方法发生报错，造成日志输出不全问题
            long time = System.currentTimeMillis() - start;
            responseAfterLog.append("<=== {}: {} ({} ms) \n");
            responseAfterArgs.add(requestMethod);
            responseAfterArgs.add(requestUrl);
            responseAfterArgs.add(time);
            responseAfterLog.append(Constant.RESPONSE_END_LOG);
            log.info(responseAfterLog.toString(), responseAfterArgs.toArray());
        }
        return result;
    }

    /**
     * 添加请求头相关的日志信息
     * @param request
     * @param log
     * @param reqBeforeArgs
     */
   private void addRequestHeader(HttpServletRequest request,StringBuilder log,List<Object> reqBeforeArgs){
       Enumeration<String> headerNames = request.getHeaderNames();
       if (headerNames != null){
         while (headerNames.hasMoreElements()){
             String name = headerNames.nextElement();
             String header = request.getHeader(name);
             log.append("== Request Header ==  {}: {} \n");
             reqBeforeArgs.add(name);
             reqBeforeArgs.add(header);
         }
       }
   }

    /**
     * 添加请求参数相关的日志信息
     * @param point
     * @param log
     * @param logValues
     */
   private void addRequestParams(ProceedingJoinPoint point,StringBuilder log,List<Object> logValues){
       Object[] args = point.getArgs();
       MethodSignature ms = (MethodSignature)point.getSignature();
       Method method = ms.getMethod();
       Map<String, Object> paramsMap = new HashMap<>(16);
       for (int i = 0; i < args.length; i++) {
           Object paramValue = args[i];
           Parameter parameter = method.getParameters()[i];
           String parameterName = parameter.getName();
           RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
           RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
           if (requestParam != null && StrUtils.isNotBlank(requestParam.value())) {
               parameterName = requestParam.value();
           }
           if (requestBody != null){
               log.append("== Request Body == {}");
               logValues.add(JsonUtil.toJson(paramValue));
           }else{
               if (paramValue instanceof HttpServletRequest){
                   paramsMap.putAll(((HttpServletRequest) paramValue).getParameterMap());
               }else if (paramValue instanceof WebRequest){
                   paramsMap.putAll(((WebRequest) paramValue).getParameterMap());
               }else if (paramValue instanceof MultipartFile){
                   String filename = ((MultipartFile) paramValue).getOriginalFilename();
                   paramsMap.put(parameterName,filename);
               }else if (paramValue instanceof MultipartFile[]){
                   MultipartFile[] files = (MultipartFile[]) paramValue;
                   if (files.length > 0){
                       List<String> filenames = Stream.of(files)
                               .map(MultipartFile::getOriginalFilename)
                               .collect(Collectors.toList());
                       paramsMap.put(parameterName,StrUtils.join(filenames));
                   }
               }else {
                   if (paramValue == null) {
                       paramsMap.put(parameterName,null);
                   } else if (paramValue instanceof InputStream) {
                       paramsMap.put(parameterName, "InputStream");
                   } else if (paramValue instanceof InputStreamSource) {
                       paramsMap.put(parameterName, "InputStreamSource");
                   } else if (JsonUtil.isCanSerialize(paramValue)) {
                       paramsMap.put(parameterName, paramValue);
                   } else {
                       paramsMap.put(parameterName, "此参数不支持序列化为json");
                   }
               }
           }
       }
       log.append("== Request Parameters ==> {} \n");
       logValues.add(JsonUtil.toJson(paramsMap));

   }

}